package dohmh.yelp.utilities;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;



public class FeedDownload {
	private String _basicAuth;
	private ArrayList<String> _compressedFile;
	private ArrayList<String> _feedFile;
	private String _feedManifestURL;
	private String _feedDir;
	private Date _dateLastGenerated = null;
	private final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
	private Proxy _proxy = null;
	
	public FeedDownload() throws ConfigurationException{
		Configuration config;
		config = new PropertiesConfiguration("download.properties");
		final String USERNAME = config.getString("USERNAME");
		final String PASSWORD = config.getString("PASSWORD");
		_feedManifestURL = config.getString("FEED_MANIFEST_URL");
		_feedDir = config.getString("FEED_DIRECTORY");
		try {
			if (config.getString("FEED_GENERATED") != null){
				_dateLastGenerated = new SimpleDateFormat(DATE_FORMAT).parse(config.getString("FEED_GENERATED"));	
			}
		} catch (ParseException e) {
		}
		File f = new File(_feedDir);
		if (!f.exists()){
			f.mkdirs();
		}
		String userpass = USERNAME + ":" + PASSWORD;
		_basicAuth = "Basic " + new String(new Base64().encode(userpass.getBytes()));
		if (config.getString("PROXY_ADDRESS") != null && config.getString("PROXY_PORT") != null
				&& !config.getString("PROXY_ADDRESS").trim().equals("") && !config.getString("PROXY_PORT").trim().equals("")){
			_proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(config.getString("PROXY_ADDRESS"), Integer.parseInt(config.getString("PROXY_PORT"))));
		}
		_compressedFile = new ArrayList<String>();
		_feedFile = new ArrayList<String>();
	}

	public void begin() throws IOException, ConfigurationException{
		downloadFeed();
		extractFeed();
		deleteCompressedFile();
		updateConfig();
	}

	// =============================================================
	// Updates the configuration file with the path to the latest feed
	// as well as the date the feed was generated.
	// =============================================================
	private void updateConfig() throws ConfigurationException{
		PropertiesConfiguration config = new PropertiesConfiguration("download.properties");
		config.setProperty("FEED_PATH", _feedFile);
		SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
		config.setProperty("FEED_GENERATED", sdf.format(_dateLastGenerated));
		config.save();
	}

	// =============================================================
	// Deletes the compressed file
	// =============================================================
	private void deleteCompressedFile(){
		for (String s : _compressedFile){
			File f = new File(s);
			if (f.exists()){
				f.delete();
			}	
		}
	}

	// =============================================================
	// Downloads the compressed feed file from Yelp's server 
	// =============================================================
	private void downloadFeed() throws IOException {
		System.out.println("Downloading feed\n------------------------------");
		ArrayList<String> feedURL = getLatestFeedURL();
		BufferedInputStream in = null;
		FileOutputStream f = null;
		String filename = null;
		URL url = null;
		URLConnection uc = null;

		for(String fUrl : feedURL){
			try {
				url = new URL(fUrl);
				if (_proxy != null){
					uc = url.openConnection(_proxy);
				}
				else {
					uc = url.openConnection();	
				}
				uc.setRequestProperty("Authorization", _basicAuth);
				in = new BufferedInputStream(uc.getInputStream());
				filename = _feedDir + fUrl.substring(fUrl.lastIndexOf("/") + 1);
				f = new FileOutputStream(filename);

				byte data[] = new byte[1024];
				int count;
				System.out.println("Writing compressed file...");
				while ((count = in.read(data, 0, 1024)) != -1)
				{
					f.write(data, 0, count);
				}
				_compressedFile.add(new File(filename).getAbsolutePath());
				System.out.println("Finished downloading feed to " + _compressedFile.get(_compressedFile.size() - 1));
			} catch (MalformedURLException e) {
				throw e;
			} catch (IOException e) {
				throw e;
			}
			finally {
				if (in != null){
					try {
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					finally {
						if (f != null){
							try {
								f.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		}
	}

	// =============================================================
	// Extracts the zipped feed and saves it as an xml file 
	// =============================================================
	private void extractFeed() throws IOException{
		System.out.println("Extracting feed...");
		FileInputStream fin = null; 
		BZip2CompressorInputStream bzIn = null;
		BufferedInputStream in = null;
		FileOutputStream f = null;
		String fFile;
		for (String cFile : _compressedFile){
			try {
				fin = new FileInputStream(cFile);
				in = new BufferedInputStream(fin);
				fFile = cFile.toLowerCase().replace(".bz2", "");
				f = new FileOutputStream(fFile);
				bzIn = new BZip2CompressorInputStream(in);
				final byte[] buffer = new byte[1024];
				int n = 0;
				while (-1 != (n = bzIn.read(buffer))) {
					f.write(buffer, 0, n);
				}
				_feedFile.add(new File(fFile).getAbsolutePath());
				System.out.println("Finished extracting feed to " + _feedFile.get(_feedFile.size() - 1));
			} catch (FileNotFoundException e) {
				throw e;
			}
			finally {
				if (bzIn != null){
					try {
						bzIn.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					finally {
						if (f != null){
							try {
								f.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		}
		
	}

	public ArrayList<String> getFeedFile(){
		return _feedFile;
	}

	// =============================================================
	// Returns an String that is the URL to the latest zipped feed file 
	// =============================================================
	@SuppressWarnings("unchecked")
	private ArrayList<String> getLatestFeedURL() {
		XMLEventReader r = null;
		ArrayList<String> retArr = new ArrayList<String>();
		try {
			r = getXML();
			XMLEvent event;
			while (r.hasNext()){
				event = r.nextEvent();
				if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals("yelpfeed_index")){
					StartElement startEl = (StartElement) event;
					Iterator iter = startEl.getAttributes();
					while (iter.hasNext()){
						Attribute attribute = (Attribute) iter.next();
						QName name = attribute.getName();
						if (name.getLocalPart().equals("time_generated")){
							String value = attribute.getValue();
							SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
							try {
								_dateLastGenerated = sdf.parse(value);
							} catch (ParseException e) {
								_dateLastGenerated = new Date();
							}
						}
					}
				}
				if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals("filename")){
					System.out.println("Latest feed URL found.");
					retArr.add(r.getElementText());
				}
			}

		} catch (ConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (XMLStreamException e) {
			e.printStackTrace();
		}
		finally {
			if (r != null){
				try {
					r.close();
				} catch (XMLStreamException e) {
				}
			}
		}
		return retArr;
	}

	// =============================================================
	// Returns an XMLEventReader that points to the beginning of the
	// feed manifest XML from yelp's servers 
	// =============================================================
	private XMLEventReader getXML() throws ConfigurationException, IOException, XMLStreamException{
		XMLEventReader xmlEventReader = null;
		BufferedReader in = null;
		URL url = null;
		URLConnection uc = null;

		url = new URL(_feedManifestURL);
		if (_proxy != null){
			uc = url.openConnection(_proxy);
		}
		else {
			uc = url.openConnection();	
		}
		uc.setRequestProperty ("Authorization", _basicAuth);
		in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
		xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(in);
		return xmlEventReader;
	}

	@SuppressWarnings("unchecked")
	public Boolean haveLatest(){
		System.out.print("Checking for latest feed... ");
		XMLEventReader r = null;
		try {
			r = getXML();
			XMLEvent event;
			while (r.hasNext()){
				event = r.nextEvent();
				if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals("yelpfeed_index")){
					StartElement startEl = (StartElement) event;
					Iterator iter = startEl.getAttributes();
					while (iter.hasNext()){
						Attribute attribute = (Attribute) iter.next();
						QName name = attribute.getName();
						if (name.getLocalPart().equals("time_generated")){
							String value = attribute.getValue();
							SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
							try {
								Date dateGenerated = sdf.parse(value);
								if (_dateLastGenerated != null && !_dateLastGenerated.before(dateGenerated)){
									return true;
								}
								else {
									return false;
								}
							} catch (ParseException e) {
								return false;
							}
						}
					}
				}
			}

		} catch (ConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (XMLStreamException e) {
			e.printStackTrace();
		}
		finally {
			System.out.println("Done.");
			if (r != null){
				try {
					r.close();
				} catch (XMLStreamException e) {
				}
			}
		}
		return false;
	}
}
